/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.    
 */
using Velocity4Net.Errors;
using System;
using System.Text;
using Velocity4Net.Runtime.Directives;
using Velocity4Net.Runtime.Logging;
namespace Velocity4Net.Runtime.Parse
{

    /**
     * This is an extension of the ParseException, which also takes a
     * template name.
     *
     * @see org.apache.velocity.runtime.parser.ParseException
     *
     * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
     * @version $Id: TemplateParseException.java 703544 2008-10-10 18:15:53Z nbubna $
     * @since 1.5
     */
    public class TemplateParseException
            : ParseException
            , ExtendedParseException
    {
        /**
         * This is the name of the template which contains the parsing error, or
         * null if not defined.
         */
        private readonly String templateName;

        /**
         * This constructor is used to add a template name
         * to info cribbed from a ParseException generated in the parser.
         * @param currentTokenVal
         * @param expectedTokenSequencesVal
         * @param tokenImageVal
         * @param templateNameVal
         */
        public TemplateParseException(Token currentTokenVal, int[][] expectedTokenSequencesVal, String[] tokenImageVal,
            String templateNameVal)
            : base(currentTokenVal, expectedTokenSequencesVal, tokenImageVal)
        {
            ;
            this.templateName = templateNameVal;
        }

        /**
         * This constructor is used by the method "generateParseException"
         * in the generated parser.  Calling this constructor generates
         * a new object of this type with the fields "currentToken",
         * "expectedTokenSequences", and "tokenImage" set.  The boolean
         * flag "specialConstructor" is also set to true to indicate that
         * this constructor was used to create this object.
         * This constructor calls its super class with the empty string
         * to force the "toString" method of parent class "Throwable" to
         * print the error message in the form:
         *     ParseException: <result of getMessage>
         * @param currentTokenVal
         * @param expectedTokenSequencesVal
         * @param tokenImageVal
         */
        public TemplateParseException(Token currentTokenVal, int[][] expectedTokenSequencesVal, String[] tokenImageVal)
            : base(currentTokenVal, expectedTokenSequencesVal, tokenImageVal)
        {
            ;
            templateName = "*unset*";
        }

        /**
         * The following constructors are for use by you for whatever
         * purpose you can think of.  Constructing the exception in this
         * manner makes the exception behave in the normal way - i.e., as
         * documented in the class "Throwable".  The fields "errorToken",
         * "expectedTokenSequences", and "tokenImage" do not contain
         * relevant information.  The JavaCC generated code does not use
         * these constructors.
         */
        public TemplateParseException()
            : base()
        {
            templateName = "*unset*";
        }

        /**
         * Creates a new TemplateParseException object.
         *
         * @param message TODO: DOCUMENT ME!
         */
        public TemplateParseException(String message)
            : base(message)
        {
            templateName = "*unset*";
        }

        /**
         * returns the Template name where this exception occured.
         * @return The Template name where this exception occured.
         */
        public String TemplateName
        {
            get
            {
                return templateName;
            }
        }

        /**
         * returns the line number where this exception occured.
         * @return The line number where this exception occured.
         */
        public int LineNumber
        {
            get
            {
                if ((currentToken != null) && (currentToken.next != null))
                {
                    return currentToken.next.beginLine;
                }
                else
                {
                    return -1;
                }
            }
        }

        /**
         * returns the column number where this exception occured.
         * @return The column number where this exception occured.
         */
        public int ColumnNumber
        {
            get
            {
                if ((currentToken != null) && (currentToken.next != null))
                {
                    return currentToken.next.beginColumn;
                }
                else
                {
                    return -1;
                }
            }
        }

        /**
         * This method has the standard behavior when this object has been
         * created using the standard constructors.  Otherwise, it uses
         * "currentToken" and "expectedTokenSequences" to generate a parse
         * error message and returns it.  If this object has been created
         * due to a parse error, and you do not catch it (it gets thrown
         * from the parser), then this method is called during the printing
         * of the final stack trace, and hence the correct error message
         * gets displayed.
         * @return The error message.
         */
        public String Message
        {
            get
            {
                if (!specialConstructor)
                {
                    StringBuilder sb = new StringBuilder(base.Message);
                    AppendTemplateInfo(sb);
                    return sb.ToString();
                }

                int maxSize = 0;

                StringBuilder expected = new StringBuilder();

                for (int i = 0; i < expectedTokenSequences.Length; i++)
                {
                    if (maxSize < expectedTokenSequences[i].Length)
                    {
                        maxSize = expectedTokenSequences[i].Length;
                    }

                    for (int j = 0; j < expectedTokenSequences[i].Length; j++)
                    {
                        expected.Append(tokenImage[expectedTokenSequences[i][j]]).Append(" ");
                    }

                    if (expectedTokenSequences[i][expectedTokenSequences[i].Length - 1] != 0)
                    {
                        expected.Append("...");
                    }

                    expected.Append(eol).Append("    ");
                }

                StringBuilder retval = new StringBuilder("Encountered \"");
                Token tok = currentToken.next;

                for (int i = 0; i < maxSize; i++)
                {
                    if (i != 0)
                    {
                        retval.Append(" ");
                    }

                    if (tok.kind == 0)
                    {
                        retval.Append(tokenImage[0]);
                        break;
                    }

                    retval.Append(add_escapes(tok.image));
                    tok = tok.next;
                }

                retval.Append("\" at ");
                AppendTemplateInfo(retval);

                if (expectedTokenSequences.Length == 1)
                {
                    retval.Append("Was expecting:").Append(eol).Append("    ");
                }
                else
                {
                    retval.Append("Was expecting one of:").Append(eol).Append("    ");
                }

                // avoid JDK 1.3 StringBuffer.Append(Object o) vs 1.4 StringBuffer.Append(StringBuffer sb) gotcha.
                retval.Append(expected.ToString());
                return retval.ToString();
            }
        }

        /**
         * @param sb
         */
        protected void AppendTemplateInfo(StringBuilder sb)
        {
            sb.Append(Log.formatFileString(TemplateName, LineNumber, ColumnNumber));
            sb.Append(eol);
        }
    }
}